package cs180battleships;

// Client side of client/server BattleShips program
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;

import javax.imageio.*;
import javax.sound.sampled.*;
import javax.swing.*;

import org.eclipse.swt.SWT;
import org.eclipse.wb.swt.SWTResourceManager;

public class BattleShipsClient extends JFrame implements Runnable, GameStateChangedListener {

	private JTextField idField; // text field to display player's mark
	private String idText;
	private JTextArea displayArea; // JTextArea to display output
	private String displayMessage;
	private JPanel boardPanel; // panel for BattleShips board
	private JPanel panel1; // panel to hold board
	private Square[][] board; // BattleShips board
	private JPanel boardPanel2; // panel for BattleShips board
	private JPanel panel2; // panel to hold board
	private Square[][] board2; // BattleShips board
	private Square currentSquare; // current square
	private Socket connection; // connection to the server
	private Scanner input; // input from server
	private Formatter output; // output to server
	private String BattleShipsHost; // host name for server
	private String myMark; // this client's mark
	private boolean myTurn; // determines which client's turn it is
	private final String X_MARK = "X"; // mark for first client
	private final String O_MARK = "O"; // mark for second client
	private ArrayList clientChangedListers = new ArrayList();


	String audioFileName;
	String explosion = "src/soundEffects/explosion-02.wav";
	String shotFired = "src/soundEffects/artiller.wav";
	String sonar = "src/soundEffects/sonar.wav";
	String splash = "src/soundEffects/splash-02.wav";
	String gameOver = "src/soundEffects/battleship.wav";

	AudioFormat audioFormat;
	AudioInputStream audioInputStream;
	SourceDataLine sourceDataLine;

	BufferedImage waterImage;
	BufferedImage hitImage;
	private BufferedImage carrierImage;
	private BufferedImage carrierVImage;
	private BufferedImage cruiserImage;
	private BufferedImage cruiserVImage;
	private BufferedImage destroyerImage;
	private BufferedImage destroyerVImage;
	private BufferedImage pTboatImage;
	private BufferedImage pTboatVImage;
	Color backgroundColor;
	Color inFrameColor;
	Color textColor;
	Font textFont;


	// public Color color;


	// set up user-interface and board
	public BattleShipsClient(String host) {
		backgroundColor=new Color(0, 0, 0);
		inFrameColor = new Color(103,103,103);
		textColor = new Color(255,255,255);
		textFont = new Font("@Adobe Gothic Std B", 3, 12);
		// color = Color.Color();
		BattleShipsHost = host; // set name of server
		displayArea = new JTextArea(4, 30); // set up JTextArea
		displayArea.setBackground(inFrameColor);
		displayArea.setForeground(textColor);
		displayArea.setFont(textFont);
		displayArea.setEditable(false);
		getContentPane().add(new JScrollPane(displayArea), BorderLayout.SOUTH);
		
		boardPanel = new JPanel(); // set up panel for squares in board
		boardPanel.setLayout(new GridLayout(10, 10, 0, 0));
		boardPanel2 = new JPanel();
		boardPanel2.setLayout(new GridLayout(10, 10, 0, 0));

		board = new Square[10][10]; // create board
		board2 = new Square[10][10]; // create board

		// loop over the rows in the board
		for (int row = 0; row < board.length; row++) {
			// loop over the columns in the board
			for (int column = 0; column < board[row].length; column++) {
				// create square
				board[row][column] = new Square(row * 10 + column);
				board2[row][column] = new Square(" ", row * 10 + column);

				boardPanel.add(board[row][column]); // add square
				boardPanel2.add(board2[row][column]); // add square
			} // end inner for
		} // end outer for

		idField = new JTextField(); // set up text field
		idField.setBackground(inFrameColor);
		idField.setForeground(textColor);
		idField.setFont(textFont);
		idField.setEditable(false);
		getContentPane().add(idField, BorderLayout.NORTH);

		panel1 = new JPanel(); // set up panel to contain the shipView
		panel1.add(boardPanel, BorderLayout.CENTER); // add board panel
		getContentPane().add(panel1, BorderLayout.WEST); // add container panel
		panel1.setBackground(inFrameColor);
		
		panel2 = new JPanel(); // set up panel to contain the targetView
		panel2.add(boardPanel2, BorderLayout.CENTER); // add board panel
		getContentPane().add(panel2, BorderLayout.EAST);
		panel2.setBackground(inFrameColor);
		getContentPane().setForeground(inFrameColor);
		getContentPane().setBackground(backgroundColor);
		// ///////////////////////////////////
		// //// GET IMAGES FOR GAMEBOARD
		try {
			// String imageName = "Explosion.jpg";
			File hitInput = new File("Explosion.jpg");
			File waterInput = new File("waterHit.jpg");
			File carrierInput = new File("carrier.jpg");
			carrierImage = ImageIO.read(carrierInput);
			File carrierVInput = new File("carrierVertical.jpg");
			carrierVImage = ImageIO.read(carrierVInput);
			File cruiserInput = new File("cruiser.jpg");
			cruiserImage = ImageIO.read(cruiserInput);
			File cruiserVInput = new File("cruiserVertical.jpg");
			cruiserVImage = ImageIO.read(cruiserVInput);
			File destroyerInput = new File("Destroyer.jpg");
			destroyerImage = ImageIO.read(destroyerInput);
			File destroyerVInput = new File("DestroyerVertical.jpg");
			destroyerVImage = ImageIO.read(destroyerVInput);
			File pTboatInput = new File("PTboat.jpg");
			pTboatImage = ImageIO.read(pTboatInput);
			File pTboatVInput = new File("PTboatVertical.jpg");
			pTboatVImage = ImageIO.read(pTboatVInput);
			File shipInput = new File("carrier.jpg");
			carrierImage = ImageIO.read(shipInput);
			hitImage = ImageIO.read(hitInput);
			waterImage = ImageIO.read(waterInput);
		} catch (IOException ie) {
			System.out.println("Error:" + ie.getMessage());
		}
		// ///////////////////////////////////
		// /// HARD CODED BATTLESHIP IMAGES

		// SHIP A
		board[0][0].squareImage = carrierImage;
		board[0][1].squareImage = carrierImage.getSubimage(30, 0, 30, 30);
		board[0][2].squareImage = carrierImage.getSubimage(60, 0, 30, 30);
		board[0][3].squareImage = carrierImage.getSubimage(90, 0, 30, 30);
		board[0][4].squareImage = carrierImage.getSubimage(120, 0, 30, 30);
		// SHIP C
		board[3][1].squareImage = pTboatVImage;
		board[4][1].squareImage = pTboatVImage.getSubimage(0, 30, 30, 30);
		// SHIP G
		board[2][8].squareImage = pTboatVImage;
		board[3][8].squareImage = pTboatVImage.getSubimage(0, 30, 30, 30);
		// SHIP B
		board[0][7].squareImage = pTboatImage;
		board[0][8].squareImage = pTboatImage.getSubimage(30, 0, 30, 30);
		// SHIP J
		board[9][6].squareImage = pTboatImage;
		board[9][7].squareImage = pTboatImage.getSubimage(30, 0, 30, 30);
		// SHIP H
		board[7][4].squareImage = cruiserImage;
		board[7][5].squareImage = cruiserImage.getSubimage(30, 0, 30, 30);
		board[7][6].squareImage = cruiserImage.getSubimage(60, 0, 30, 30);
		board[7][7].squareImage = cruiserImage.getSubimage(90, 0, 30, 30);
		// SHIP F
		board[1][6].squareImage = cruiserVImage;
		board[2][6].squareImage = cruiserVImage.getSubimage(0, 30, 30, 30);
		board[3][6].squareImage = cruiserVImage.getSubimage(0, 60, 30, 30);
		board[4][6].squareImage = cruiserVImage.getSubimage(0, 90, 30, 30);
		// SHIP E
		board[8][2].squareImage = destroyerImage;
		board[8][3].squareImage = destroyerImage.getSubimage(30, 0, 30, 30);
		board[8][4].squareImage = destroyerImage.getSubimage(60, 0, 30, 30);
		// SHIP D
		board[4][3].squareImage = destroyerVImage;
		board[5][3].squareImage = destroyerVImage.getSubimage(0, 30, 30, 30);
		board[6][3].squareImage = destroyerVImage.getSubimage(0, 60, 30, 30);
		// SHIP I
		board[5][9].squareImage = destroyerVImage;
		board[6][9].squareImage = destroyerVImage.getSubimage(0, 30, 30, 30);
		board[7][9].squareImage = destroyerVImage.getSubimage(0, 60, 30, 30);

		setSize(650, 500); // set size of window
		setVisible(true); // show window

		startClient();
	} // end BattleShipsClient constructor


	// start the client thread
	public void startClient() {
		try {
			// make connection to server
			// connection = new Socket(
			// InetAddress.getByName( BattleShipsHost ), 12345 );
			connection = new Socket(BattleShipsHost, 12345);

			// get streams for input and output
			input = new Scanner(connection.getInputStream());
			output = new Formatter(connection.getOutputStream());
		} // end try
		catch (IOException ioException) {
			ioException.printStackTrace();
		} // end catch

		// create and start worker thread for this client
		ExecutorService worker = Executors.newFixedThreadPool(1);
		worker.execute(this); // execute client
	} // end method startClient


	// control thread that allows continous update of displayArea
	public void run() {
		myMark = input.nextLine(); // get player's mark (X or O)
		final String playerNumber;
		if(myMark.equals("X")){
			playerNumber="1";
		}else{
			playerNumber="2";	
		}
		SwingUtilities.invokeLater(new Runnable() {

			public void run() {
				// display player's mark
				setIdText("You are Player\"" + playerNumber + "\"");
				idField.setText("You are Player\"" + playerNumber + "\"");
			} // end method run
		} // end anonymous inner class
				); // end call to SwingUtilities.InvokeLater

		myTurn = (myMark.equals(X_MARK)); // determine if client's turn

		// receive messages sent to client and output them
		while (true) {
			if (input.hasNextLine())
				processMessage(input.nextLine());
		} // end while
	} // end method run


	// process messages received by client
	private void processMessage(String message) {
		// valid move occurred
		if (message.equals("Valid move.")) {
			displayMessage("Valid move, please wait.\n");
			setMark(currentSquare, myMark); // set mark in square
		} else if (message.equals("Direct Hit")) {
			displayMessage("Direct Hit");
			playAudio(explosion);
			setMark(currentSquare, hitImage); // set mark in square
		} else if (message.equals("Shot hit the water")) {
			displayMessage("Shot hit the water");
			playAudio(splash);
			setMark(currentSquare, waterImage); // set mark in square
		} else if (message.equals("Opponent missed")) {
			displayMessage("Opponent missed. Your turn.\n");
			playAudio(splash);
			int location = input.nextInt(); // get move location
			input.nextLine(); // skip newline after int location
			int row = location / 10; // calculate row
			int column = location % 10; // calculate column
			board[row][column].setImage(waterImage);
			myTurn = true; // now this clients turn
		} else if (message.equals("You were hit!")) {
			displayMessage("You were hit! Your turn.\n");
			playAudio(explosion);
			int location = input.nextInt(); // get move location
			input.nextLine(); // skip newline after int location
			int row = location / 10; // calculate row
			int column = location % 10; // calculate column
			board[row][column].setImage(hitImage);
			myTurn = true; // now this clients turn
		} else if (message.equals("Invalid move, try again")) {
			displayMessage(message + "\n"); // display invalid move
			myTurn = true; // still this client's turn
			playAudio(sonar);
		} // end else if
		else if (message.equals("Opponent moved")) {
			int location = input.nextInt(); // get move location
			input.nextLine(); // skip newline after int location
			int row = location / 10; // calculate row
			int column = location % 10; // calculate column

			setMark(board[row][column], (myMark.equals(X_MARK) ? O_MARK : X_MARK)); // mark move
			displayMessage("Opponent moved. Your turn.\n");
			myTurn = true; // now this clients turn
		} else if (message.contains("Game Over!")) {
			playAudio(gameOver);
			displayMessage("Game Over.\n");
		}// end else if
		else
			displayMessage(message + "\n"); // display the message
	} // end method processMessage


	// manipulate displayArea in event-dispatch thread
	private void displayMessage(final String messageToDisplay) {
		SwingUtilities.invokeLater(new Runnable() {

			public void run() {
				displayArea.append(messageToDisplay); // updates output
				setDisplayMessage(messageToDisplay);

			} // end method run
		} // end inner class
				); // end call to SwingUtilities.invokeLater
	} // end method displayMessage


	// utility method to set mark on board in event-dispatch thread
	private void setMark(final Square squareToMark, final String mark) {
		SwingUtilities.invokeLater(new Runnable() {

			public void run() {
				squareToMark.setMark(mark); // set mark in square
			} // end method run
		} // end anonymous inner class
				); // end call to SwingUtilities.invokeLater
	} // end method setMark


	// utility method to set mark on board in event-dispatch thread
	private void setMark(final Square squareToMark, final BufferedImage image) {
		SwingUtilities.invokeLater(new Runnable() {

			public void run() {
				squareToMark.setImage(image);
			} // end method run
		} // end anonymous inner class
				); // end call to SwingUtilities.invokeLater
	} // end method setMark
		// send message to server indicating clicked square


	public void sendClickedSquare(int location) {
		// if it is my turn
		if (myTurn) {
			// playAudio(shotFired);
			output.format("%d\n", location); // send location to server
			output.flush();
			myTurn = false; // not my turn any more
		} // end if
	} // end method sendClickedSquare


	// set current Square
	public void setCurrentSquare(Square square) {
		currentSquare = square; // set current square to argument
	} // end method setCurrentSquare


	// / private inner class for the square on the board
	@SuppressWarnings("serial")
	protected class Square extends JPanel {

		private String mark; // mark to be drawn in this square
		private int location; // location of square
		BufferedImage squareImage;


		public Square(String squareMark, int squareLocation) {
			mark = squareMark; // set mark for this square
			location = squareLocation; // set location of this square


			addMouseListener(new MouseAdapter() {

				public void mouseReleased(MouseEvent e) {
					setCurrentSquare(Square.this); // set current square

					// send location of this square
					sendClickedSquare(getSquareLocation());
				} // end method mouseReleased
			} // end anonymous inner class
			); // end call to addMouseListener
		} // end Square constructor


		public Square(int squareLocation)// Constructor for users ship view (not for clicking)
		{
			location = squareLocation; // set location of this square
		} // end ship view Square constructor


		// return preferred size of Square
		public Dimension getPreferredSize() {
			return new Dimension(30, 30); // return preferred size
		} // end method getPreferredSize


		// return minimum size of Square
		public Dimension getMinimumSize() {
			return getPreferredSize(); // return preferred size
		} // end method getMinimumSize


		// set mark for Square
		public void setMark(String newMark) {
			mark = newMark; // set mark of square
			repaint(); // repaint square
		} // end method setMark


		public void setImage(BufferedImage image) {
			squareImage = image;
			repaint();
		}


		// return Square location
		public int getSquareLocation() {
			return location; // return location of square
		} // end method getSquareLocation


		// draw Square
		public void paintComponent(Graphics g) {
			super.paintComponent(g);
			g.setColor(new Color(0, 162, 232));
			g.fillRect(0, 0, 29, 29); // draw square
			g.drawImage(squareImage, 0, 0, null);
			// g.drawString( mark, 11, 20); // draw mark
		} // end method paintComponent
	} // end inner-class Square


	@SuppressWarnings("unchecked")
	public void addClientChangedListener(ClientChangedListener listener) {
		clientChangedListers.add(listener);
	}


	/**
	 * Allows an observer to unregister for BalanceChanged events.
	 */
	public void removeClientChangedListener(ClientChangedListener listener) {
		clientChangedListers.remove(listener);
	}


	/**
	 * Iterates through all listeners, informing them that a BalanceChanged event has occurred.
	 */
	private void fireClientChanged() {
		for (int i = 0; i < clientChangedListers.size(); i++) {
			ClientChangedListener listener = (ClientChangedListener) clientChangedListers.get(i);
			listener.clientChanged(this);
		}
	}


	public void setDisplayMessage(String displayMessage) {
		this.displayMessage = displayMessage;
		fireClientChanged();
	}


	public String getDisplayMessage() {
		return displayMessage;
	}


	public void setIdText(String idText) {
		this.idText = idText;
	}


	public String getIdText() {
		return idText;
	}


	private void playAudio(String Sound) {
		try {
			File soundFile = new File(Sound);
			audioInputStream = AudioSystem.getAudioInputStream(soundFile);
			audioFormat = audioInputStream.getFormat();
			// System.out.println(audioFormat);

			DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, audioFormat);

			sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);

			// Create a thread to play back the data and
			// start it running. It will run until the
			// end of file, or the Stop button is
			// clicked, whichever occurs first.
			// Because of the data buffers involved,
			// there will normally be a delay between
			// the click on the Stop button and the
			// actual termination of playback.
			new PlayThread().start();
		} catch (Exception e) {
			e.printStackTrace();
			// System.exit(0);
		}// end catch
	}// end playAudio


	// =============================================//
	// Inner class to play back the data from the
	// audio file.
	class PlayThread extends Thread {

		byte tempBuffer[] = new byte[10000];


		public void run() {
			try {
				sourceDataLine.open(audioFormat);
				sourceDataLine.start();

				int cnt;
				// Keep looping until the input read method
				// returns -1 for empty stream or the
				// user clicks the Stop button causing
				// stopPlayback to switch from false to
				// true.
				while ((cnt = audioInputStream.read(tempBuffer, 0, tempBuffer.length)) != -1)
					// && stopPlayback == false) {
					// if (cnt > 0) {
					// Write data to the internal buffer of
					// the data line where it will be
					// delivered to the speaker.
					sourceDataLine.write(tempBuffer, 0, cnt);
				// }// end if
				// }// end while
				// Block and wait for internal buffer of the
				// data line to empty.
				sourceDataLine.drain();
				sourceDataLine.close();

			} catch (Exception e) {
				e.printStackTrace();
				System.exit(0);
			}// end catch
		}// end run
	}// end inner class PlayThread
		// ===================================//


	@Override
	public void gameStateChanged(GameState gameState) {
		// TODO Auto-generated method stub
		playAudio(gameState.getAudioFileName());
	}
} // end class BattleShipsClient
